/*
*  linux/kernel/chr_drv/tty_ioctl.c
*
*  (C) 1991  Linus Torvalds
*/

#include <errno.h>
#include <termios.h>

#include <linux\sched.h>
#include <linux\kernel.h>
#include <linux\tty.h>

#include <asm\io.h>
#include <asm\segment.h>
#include <asm\system.h>

static unsigned short quotient[] = {
	0, 2304, 1536, 1047, 857,
	768, 576, 384, 192, 96,
	64, 48, 24, 12, 6, 3
};

static void change_speed(struct tty_struct * tty)
{
	unsigned short port, quot;

	if (!(port = (unsigned short)tty->read_q.data))
		return;
	quot = quotient[tty->termios.c_cflag & CBAUD];
	cli();
	outb_p(0x80, port + 3);		/* set DLAB */
	outb_p(quot & 0xff, port);	/* LS of divisor */
	outb_p(quot >> 8, port + 1);	/* MS of divisor */
	outb(0x03, port + 3);		/* reset DLAB */
	sti();
}

static void flush(struct tty_queue * queue)
{
	cli();
	queue->head = queue->tail;
	sti();
}

static void wait_until_sent(struct tty_struct * tty)
{
	/* do nothing - not implemented */
}

static void send_break(struct tty_struct * tty)
{
	/* do nothing - not implemented */
}

static int get_termios(struct tty_struct * tty, struct termios * termios)
{
	int i;

	verify_area(termios, sizeof (*termios));
	for (i = 0; i < (sizeof (*termios)); i++)
		put_fs_byte(((char *)&tty->termios)[i], i + (char *)termios);
	return 0;
}

static int set_termios(struct tty_struct * tty, struct termios * termios)
{
	int i;

	for (i = 0; i < (sizeof (*termios)); i++)
		((char *)&tty->termios)[i] = get_fs_byte(i + (char *)termios);
	change_speed(tty);
	return 0;
}

static int get_termio(struct tty_struct * tty, struct termio * termio)
{
	int i;
	struct termio tmp_termio;

	verify_area(termio, sizeof (*termio));
	tmp_termio.c_iflag = (unsigned short)tty->termios.c_iflag;
	tmp_termio.c_oflag = (unsigned short)tty->termios.c_oflag;
	tmp_termio.c_cflag = (unsigned short)tty->termios.c_cflag;
	tmp_termio.c_lflag = (unsigned short)tty->termios.c_lflag;
	tmp_termio.c_line = tty->termios.c_line;
	for (i = 0; i < NCC; i++)
		tmp_termio.c_cc[i] = tty->termios.c_cc[i];
	for (i = 0; i < (sizeof (*termio)); i++)
		put_fs_byte(((char *)&tmp_termio)[i], i + (char *)termio);
	return 0;
}

/*
* This only works as the 386 is low-byt-first
*/
static int set_termio(struct tty_struct * tty, struct termio * termio)
{
	int i;
	struct termio tmp_termio;

	for (i = 0; i < (sizeof (*termio)); i++)
		((char *)&tmp_termio)[i] = get_fs_byte(i + (char *)termio);
	*(unsigned short *)&tty->termios.c_iflag = tmp_termio.c_iflag;
	*(unsigned short *)&tty->termios.c_oflag = tmp_termio.c_oflag;
	*(unsigned short *)&tty->termios.c_cflag = tmp_termio.c_cflag;
	*(unsigned short *)&tty->termios.c_lflag = tmp_termio.c_lflag;
	tty->termios.c_line = tmp_termio.c_line;
	for (i = 0; i < NCC; i++)
		tty->termios.c_cc[i] = tmp_termio.c_cc[i];
	change_speed(tty);
	return 0;
}

int tty_ioctl(int dev, int cmd, int arg)
{
	struct tty_struct * tty;
	if (MAJOR(dev) == 5) {
		dev = current->tty;
		if (dev < 0)
			panic("tty_ioctl: dev<0");
	}
	else
		dev = MINOR(dev);
	tty = dev + tty_table;
	switch (cmd) {
	case TCGETS:
		return get_termios(tty, (struct termios *) arg);
	case TCSETSF:
		flush(&tty->read_q); /* fallthrough */
	case TCSETSW:
		wait_until_sent(tty); /* fallthrough */
	case TCSETS:
		return set_termios(tty, (struct termios *) arg);
	case TCGETA:
		return get_termio(tty, (struct termio *) arg);
	case TCSETAF:
		flush(&tty->read_q); /* fallthrough */
	case TCSETAW:
		wait_until_sent(tty); /* fallthrough */
	case TCSETA:
		return set_termio(tty, (struct termio *) arg);
	case TCSBRK:
		if (!arg) {
			wait_until_sent(tty);
			send_break(tty);
		}
		return 0;
	case TCXONC:
		return -EINVAL; /* not implemented */
	case TCFLSH:
		if (arg == 0)
			flush(&tty->read_q);
		else if (arg == 1)
			flush(&tty->write_q);
		else if (arg == 2) {
			flush(&tty->read_q);
			flush(&tty->write_q);
		}
		else
			return -EINVAL;
		return 0;
	case TIOCEXCL:
		return -EINVAL; /* not implemented */
	case TIOCNXCL:
		return -EINVAL; /* not implemented */
	case TIOCSCTTY:
		return -EINVAL; /* set controlling term NI */
	case TIOCGPGRP:
		verify_area((void *)arg, 4);
		put_fs_long(tty->pgrp, (unsigned long *)arg);
		return 0;
	case TIOCSPGRP:
		tty->pgrp = get_fs_long((unsigned long *)arg);
		return 0;
	case TIOCOUTQ:
		verify_area((void *)arg, 4);
		put_fs_long(CHARS(tty->write_q), (unsigned long *)arg);
		return 0;
	case TIOCINQ:
		verify_area((void *)arg, 4);
		put_fs_long(CHARS(tty->secondary),
			(unsigned long *)arg);
		return 0;
	case TIOCSTI:
		return -EINVAL; /* not implemented */
	case TIOCGWINSZ:
		return -EINVAL; /* not implemented */
	case TIOCSWINSZ:
		return -EINVAL; /* not implemented */
	case TIOCMGET:
		return -EINVAL; /* not implemented */
	case TIOCMBIS:
		return -EINVAL; /* not implemented */
	case TIOCMBIC:
		return -EINVAL; /* not implemented */
	case TIOCMSET:
		return -EINVAL; /* not implemented */
	case TIOCGSOFTCAR:
		return -EINVAL; /* not implemented */
	case TIOCSSOFTCAR:
		return -EINVAL; /* not implemented */
	default:
		return -EINVAL;
	}
}
